#include <ucontext.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "list.h"
#include "thread.h"
#include <valgrind/valgrind.h>
#include <signal.h>
#include <sys/time.h>


#define TIMESLICE 500000 //usec

struct thread_elt *dead_begin=NULL;
struct thread_elt *ready_begin=NULL; //file
struct thread_elt *cursor_ready=NULL; //unique element

struct sigaction sig_alarm_blocked;// structure contenant le gestionnaire de signal ignorant SIGALRM
struct sigaction sig_alarm_yield;//structure contenant le gestionnaire de signal faisant un yield après avoir recu SIGVTALRM
struct itimerval timeslice;

//gestionnaire de signal pour la préemption
static void handler(int signum) {
  thread_yield();
}

//gestionnaire de signal bloquant la préemption
static void handler2(int signum) {
 
}

//blocage du signal SIGVTALRM
static void deactivate_sigalrm(){
  sigaction(SIGVTALRM,&sig_alarm_blocked,NULL);
}

//déblocage du signal SIGVTALRM
static void reactivate_sigalrm(){
  sigaction(SIGVTALRM,&sig_alarm_yield,NULL);
}

static void thread_init()
{
   if(cursor_ready==NULL)
    {
      /*
       * initialisation des gestionnaires
       */ 
      memset(&sig_alarm_blocked,0,sizeof(sig_alarm_blocked));
      memset(&sig_alarm_yield,0,sizeof(sig_alarm_yield));
      sig_alarm_blocked.sa_handler=handler2;
      sig_alarm_yield.sa_handler=handler;
      /*
       * initialisation  timeslice
       */
      timeslice.it_interval.tv_sec=0;
      timeslice.it_interval.tv_usec=TIMESLICE;
       timeslice.it_value.tv_sec=0;
      timeslice.it_value.tv_usec=TIMESLICE;

      /*
       * initialisation du thread_elt du main
       */
      struct thread_elt * elt=malloc(sizeof(*elt));
      elt->waiting_begin=NULL;
      elt->next=NULL;
      elt->retval=NULL;
      elt->dead=0;
      ready_begin=elt;
      cursor_ready=elt;
      
    }
}


void thread_free(thread_t t){
  
  printf("free %p\n",t);
  deactivate_sigalrm();
  VALGRIND_STACK_DEREGISTER(t->valgrind_stackid);
  free(t->context.uc_stack.ss_sp);
  free(t);
  reactivate_sigalrm();
}


int thread_create(thread_t *newthread, void *(*func)(void *), void *funcarg){
  
  thread_init(); 
  deactivate_sigalrm();
  struct thread_elt *t;

  t=malloc(sizeof(*t)); 
  getcontext(&(t->context));
  t->context.uc_stack.ss_size = 64*1024;
  t->context.uc_stack.ss_sp = malloc(t->context.uc_stack.ss_size);
  t->valgrind_stackid = VALGRIND_STACK_REGISTER(t->context.uc_stack.ss_sp,
						t->context.uc_stack.ss_sp + t->context.uc_stack.ss_size);
  t->context.uc_link = &cursor_ready->context;
  makecontext(&(t->context),(void(*)(void))func,1,funcarg);

  t->waiting_begin=NULL;
  t->next=NULL;
  t->retval=NULL;
  t->dead=0;
  *newthread = t;

  add_list(*newthread,&ready_begin);
  thread_yield();
  reactivate_sigalrm();
  return 0;
}

thread_t thread_self(void){  
  
  thread_init();
  return cursor_ready;
}


int thread_yield(void){
  printf("YIELD : %p \n",thread_self());
  
  deactivate_sigalrm();
  if(cursor_ready == NULL)
    {
      reactivate_sigalrm();
      return 0;
    }
  
  struct thread_elt * t = cursor_ready;
  
  if(cursor_ready->next != NULL)
    cursor_ready = cursor_ready->next;
  else {
    cursor_ready = ready_begin;
  }

  reactivate_sigalrm();
  //préemption
  setitimer(ITIMER_VIRTUAL,&timeslice,NULL);
  swapcontext(&(t->context), &(cursor_ready->context)); 
  return 1;
}



int thread_join(thread_t thread, void **retval){
  deactivate_sigalrm();
  struct thread_elt *next_elt, * t = cursor_ready;
      
  if(cursor_ready == NULL)
    {
      reactivate_sigalrm();
      return 0;
    }

  if(thread->dead)
    {
      if(retval!=NULL)
	*retval = *(thread->retval);
      remove_list(thread, &dead_begin);
      thread_free(thread);
      reactivate_sigalrm();
      thread_yield();
      return 0;
    }
  else
    {
      if(cursor_ready->next != NULL)
	next_elt = cursor_ready->next;
      else 
	next_elt = ready_begin;
      
      getcontext(&(t->context));
      
      remove_list(cursor_ready, &ready_begin);
      cursor_ready = next_elt;
      
      add_list(t, &(thread->waiting_begin));
      t->next = NULL;
      
      t->retval = retval;
      reactivate_sigalrm();
      //préemption
      setitimer(ITIMER_VIRTUAL, &timeslice,NULL);
      swapcontext(&(t->context), &(cursor_ready->context));
      thread_free(thread);

      return 0;
      
    }
}


void thread_exit(void *retval) {
  deactivate_sigalrm();
  struct thread_elt *tmp = cursor_ready->waiting_begin;
  struct thread_elt *former_crs = cursor_ready;
  
  cursor_ready->dead = 1;

  if(tmp != NULL) {
    
    while(tmp != NULL) {
      if(tmp->retval!=NULL)
	*(tmp->retval) = retval;
      tmp = tmp->next;

    }
    add_list(cursor_ready->waiting_begin, &ready_begin);
    cursor_ready->waiting_begin = NULL;
    
    if(cursor_ready->next != NULL)
      cursor_ready = cursor_ready->next;
    else 
      cursor_ready = ready_begin;
    remove_list(former_crs, &ready_begin);
    
    reactivate_sigalrm();
    //préemption
    setitimer(ITIMER_VIRTUAL, &timeslice,NULL);
    setcontext(&(cursor_ready->context));
  }
  else
    { 
      if(cursor_ready->next != NULL)
	cursor_ready = cursor_ready->next;
      else 
	cursor_ready = ready_begin;

      remove_list(former_crs, &ready_begin);
      add_list(former_crs, &dead_begin);
      
      former_crs->retval=malloc(sizeof(void *));
      *(former_crs->retval) = retval;
     
      
      reactivate_sigalrm();
      //préemption
      setitimer(ITIMER_VIRTUAL, &timeslice,NULL);
      setcontext(&(cursor_ready->context));
    }
}









